﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using log4net;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Index;
using Lucene.Net.QueryParsers;
using Lucene.Net.Search;
using Lucene.Net.Store;
using SnapUtilities;

namespace LuceneSearcher
{
    public class SynonymLuceneSearcher : ISearcher
    {
        public const String FSyn = "syn";
        public const String FWord = "word";

        private static readonly ILog Logger =
            LogManager.GetLogger( System.Reflection.MethodBase.GetCurrentMethod().DeclaringType );
        private readonly Stopwatch stopwatch = new Stopwatch();
        private const int MaxNumberSearchTerms = 500;

        private readonly Directory newsDirectory;
        private readonly Directory synonymDirectory;

        public SynonymLuceneSearcher(Directory newsDirectory, Directory synonymDirectory)
        {
            this.newsDirectory = newsDirectory;
            this.synonymDirectory = synonymDirectory;
        }

        #region Implementation of ISearcher

        public IEnumerable<SearchResult> SearchNewsItems( DateTime splitDate )
        {
            Logger.DebugFormat("Synonym-Lucene Searching with news items older than {0}...", splitDate);
            var results = new List<SearchResult>();
            var indexSearcher = new IndexSearcher(newsDirectory, true);
            var parser = new QueryParser( Lucene.Net.Util.Version.LUCENE_29, "body",
                                          new StandardAnalyzer( Lucene.Net.Util.Version.LUCENE_29 ) );
            var newsInfos = Utilities.PullSingleCompanyNewsInfo( splitDate, DateTime.MaxValue );
            
            stopwatch.Restart();
            foreach (var newsId in newsInfos.Keys)
            {
                var searchResult = new SearchResult();
                searchResult.NewsId = newsId;
                var searchStrings = PrepareSearchStrings( newsInfos[newsId] );
                foreach (var searchString in searchStrings)
                {
                    if (String.IsNullOrEmpty( searchString )) continue;
                    var query = parser.Parse( searchString );
                    var hits = indexSearcher.Search( query, 1 );
                    if (hits.scoreDocs.Length == 0) continue;

                    var document = indexSearcher.Doc( hits.scoreDocs[0].doc );
                    searchResult.BestMatchNewsId = Int32.Parse( document.Get( "id" ) );
                    searchResult.BestMatchScore = hits.scoreDocs[0].score;
                    results.Add( searchResult );
                }
            }
            stopwatch.Stop();
            Logger.DebugFormat( "Found {0} result sets in {1}", results.Count, stopwatch.Elapsed );

            return results;//.Distinct();
        }

        private IEnumerable<string> PrepareSearchStrings(string newsInfo)
        {
            var cleanedText = CleanStringForParsing(newsInfo);
            var words = cleanedText.Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
            var wordsToSynonyms = ExpandToIncludeSynonyms( words );
            var paragraphs = new List<string>();
            for (int i = 0; i <= wordsToSynonyms.Count / MaxNumberSearchTerms; i++)
            {
                var builder = new StringBuilder();
                foreach (var word in wordsToSynonyms.Skip(MaxNumberSearchTerms * i).Take(MaxNumberSearchTerms))
                {
                    builder.AppendFormat("{0} ", word);
                }
                paragraphs.Add(builder.ToString());
            }

            return paragraphs;
        }

        private IList<string> ExpandToIncludeSynonyms( IEnumerable<string> words )
        {
            
            var wordsWithSynonyms = new List<string>();
            foreach (var word in words)
            {
                wordsWithSynonyms.Add( word );
                wordsWithSynonyms.AddRange( GetSynonyms( word ) );
            }

            return wordsWithSynonyms;
        }

        private IEnumerable<string> GetSynonyms( string word )
        {
            var synonyms = new List<string>();
            var synSearcher = new IndexSearcher(synonymDirectory, true);
            var query = new TermQuery( new Term( FWord, word ) );
            var hits = synSearcher.Search( query, 10 ).scoreDocs;
            foreach (var scoreDoc in hits)
            {
                var doc = synSearcher.Doc( scoreDoc.doc );
                var values = doc.GetValues( FSyn );
                synonyms.AddRange( values );
            }

            return synonyms;
        }

        private static string CleanStringForParsing(string text)
        {
            // Remove metasearch symbols that Lucene may misconstrue
            //  as search operators.
            text = text.Replace("(", String.Empty)
                .Replace(")", String.Empty)
                .Replace("-", String.Empty)
                .Replace("+", String.Empty)
                .Replace("*", String.Empty)
                .Replace("?", String.Empty)
                .Replace("]", String.Empty)
                .Replace("[", String.Empty)
                .Replace("{", String.Empty)
                .Replace("}", String.Empty)
                .Replace("<", String.Empty)
                .Replace(">", String.Empty)
                .Replace("“", "")
                .Replace("”", "");

            // Remove newline and linefeed symbols
            text = text.Replace(Environment.NewLine, " ");
            text = text.Replace("\n", " ");
            text = text.Replace("\r", " ");




            if (String.IsNullOrEmpty(text)) return String.Empty;

            if (!Regex.IsMatch(text, "[a-z]+", RegexOptions.IgnoreCase)) return String.Empty;

            return QueryParser.Escape(text);
        }

        #endregion
    }
}
